#pragma once

#include <thread>
#include <atomic>
#include <condition_variable>
#include <vector>

extern "C" {
#include <log/log.h>
}

enum class EventLoopEnum {
    Pause,
    Resume,
    Reset,
};

class EventLoop {
public:
    ~EventLoop() {
        reset();
    };

    void pause() {
        LOGD("eee pause  start");
        std::unique_lock<std::mutex> lock{stateMutex};
        bool b = isInLoop();
        LOGD("eee pause isInLoop %s", b ? "true" : "false");
        state.store(EventLoopEnum::Pause);
        stateSignal.notify_all();
        stateSignal.wait(lock,
                         [this, b] {
                             return pauseSize.load() >=
                                    (b ? loopSize.load() - 1 : loopSize.load()) ||
                                    state.load() != EventLoopEnum::Pause;
                         });
        LOGD("eee pause  end");
    }

    void resume() {
        LOGD("eee resume  start");
        std::unique_lock<std::mutex> lock{stateMutex};
        state.store(EventLoopEnum::Resume);
        stateSignal.notify_all();
        pauseSignal.notify_all();
        stateSignal.wait(lock,
                         [this] {
                             return pauseSize.load() == 0 || state.load() != EventLoopEnum::Resume;
                         });
        LOGD("eee resume  end");
    }

    void reset() {
        LOGD("eee reset  start");
        std::unique_lock<std::mutex> lock{stateMutex};
        bool b = isInLoop();
        LOGD("eee pause isInLoop %s", b ? "true" : "false");
        state.store(EventLoopEnum::Reset);
        stateSignal.notify_all();
        pauseSignal.notify_all();
        stateSignal.wait(lock,
                         [this, b] {
                             return loopSize.load() <= (b ? 1 : 0) ||
                                    state.load() != EventLoopEnum::Reset;
                         });
        LOGD("eee reset  end");
    }

    template<class Fn, class ...Arg>
    auto operator()(Fn &&fn, Arg &&... arg) {
        return [=]() -> void {
            loopSize++;
            LOGD("eee start loop size %d", loopSize.load());
            insetThreadID();
            while (true) {
                switch (state.load()) {
                    case EventLoopEnum::Pause :
                        pauseSize++;
                        LOGD("eee pause size %d", pauseSize.load());
                        stateSignal.notify_all();
                        {
                            std::unique_lock<std::mutex> lock{stateMutex};
                            pauseSignal.wait(lock,
                                             [this] {
                                                 return state.load() != EventLoopEnum::Pause;
                                             });
                        }
                        break;
                    case EventLoopEnum::Resume :
                        if (pauseSize.load() > 0) {
                            pauseSize--;
                            LOGD("eee pause size %d", pauseSize.load());
                            stateSignal.notify_all();
                        }
                        fn(arg...);
                        break;
                    case EventLoopEnum::Reset :
                        if (loopSize.load() > 0) {
                            if (pauseSize.load() > 0) pauseSize--;
                            loopSize--;
                            LOGD("eee end loop size %d", loopSize.load());
                            LOGD("eee end pause size %d", pauseSize.load());
                            removeThreadId();
                            stateSignal.notify_all();
                        }
                        return;
                }
            }
        };
    }

private:
    std::atomic_int loopSize{0};
    std::atomic_int pauseSize{0};
    std::atomic<EventLoopEnum> state{EventLoopEnum::Pause};
    std::vector<std::thread::id> threadIdVector{};
    std::mutex stateMutex;
    std::mutex threadIMutex;
    std::condition_variable stateSignal;
    std::condition_variable pauseSignal;

    void insetThreadID() {
        std::lock_guard<std::mutex> lock{threadIMutex};
        threadIdVector.push_back(std::this_thread::get_id());
    }

    void removeThreadId() {
        std::lock_guard<std::mutex> lock{threadIMutex};
        auto thisThreadId = std::this_thread::get_id();
        for (auto item = threadIdVector.begin(); item != threadIdVector.end();) {
            if (*item == thisThreadId)
                item = threadIdVector.erase(item);
            else
                item++;
        }
    }

    bool isInLoop() {
        std::lock_guard<std::mutex> lock{threadIMutex};
        auto thisThreadId = std::this_thread::get_id();
        for (auto item = threadIdVector.begin(); item != threadIdVector.end(); item++)
            if (*item == thisThreadId) return true;
        return false;
    }
};